package com.liuhm.utils;


import cn.hutool.core.util.TypeUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.liuhm.VO.DataFilePreVO;
import com.liuhm.entity.TableField;
import com.liuhm.exception.BDException;
import com.univocity.parsers.csv.CsvParser;
import com.univocity.parsers.csv.CsvParserSettings;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.thymeleaf.util.StringUtils;

import java.io.*;
import java.math.BigDecimal;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * csv工具
 *
 * @author liuhaomin
 * @date 2020/10/24
 */
@Slf4j
public class CsvUtils {
  /*  文件大小	JavaCSV解析耗时	niVocity-parsers解析耗时
        10MB, 145453 行	1138ms	1138ms
        100MB, 809008 行	23s	6s
        434MB, 4499959 行	91s	28s
        1GB, 23803502 行	245s	70s*/

    //  1     399999  1829
//  2     3299999  12876
//  3     3299999
//  4     30303501
    public static void main(String[] args) {


        // 创建csv解析器settings配置对象
        CsvParserSettings settings = new CsvParserSettings();
        // 文件中使用 '\n' 作为行分隔符
        // 确保像MacOS和Windows这样的系统
        // 也可以正确处理（MacOS使用'\r'；Windows使用'\r\n'）
        settings.getFormat().setLineSeparator("\n");
        String relativePath="/testcsv3.csv";

        int i=1;
        int len=1000000;
        int start=1;
        Long startTime=System.currentTimeMillis();
        System.out.println("开始"+startTime);
        CsvParser csvParser = new CsvParser(settings);
        csvParser.beginParsing(getReader(relativePath));
        String[] row;
        while ((row = csvParser.parseNext()) != null) {
            System.out.println(Arrays.toString(row));
        }
        System.out.println("结束"+"asdasdasd  "+System.currentTimeMillis());
        System.out.println("结束"+"------  "+(System.currentTimeMillis()-startTime));
        csvParser.stopParsing();
       /* while (true){
            // 创建CSV解析器（将分隔符传入对象）
            CsvParser parser = new CsvParser(settings);
            parser.beginParsing(getReader(relativePath));
            parser.getContext().skipLines((i -1)*len+start+1);
            if(parser.parseNext()==null){
                break;
            }
            int finalI = i;
            Thread thread= new Thread(() -> {
                CsvParser csvParser = new CsvParser(settings);
                csvParser.beginParsing(getReader(relativePath));
                csvParser.getContext().skipLines((finalI -1)*len+start+1);
                String[] row;
                int j=0;
                while ((row = csvParser.parseNext()) != null&&j<len) {
                    System.out.println(finalI+"asda"+Arrays.toString(row));
                    j++;
                }
                System.out.println("结束"+finalI+"asdasdasd  "+System.currentTimeMillis());
                System.out.println("结束"+finalI+"------  "+(System.currentTimeMillis()-startTime));
                csvParser.stopParsing();

            });
            thread.start();
            thread.setName(String.valueOf(i));
            i++;
        }*/

        // 调用beginParsing逐个读取记录，使用迭代器iterator
      /*  parser.beginParsing(getReader("/testcsv.csv"));
        String[] row;
        while ((row = parser.parseNext()) != null) {
            System.out.println(Arrays.toString(row));
        }*/

        // 在读取结束时自动关闭所有资源，
        // 或者当错误发生时，可以在任何使用调用stopParsing()

        // 只有在不是读取所有内容的情况下调用下面方法
        // 但如果不调用也没有非常严重的问题
//        parser.stopParsing();
    }

    /**
      * 生成csv文件
      * @param
      * @return
      */
    @Test
    public void writeTest() throws IOException {
        FileWriter writer2 = new FileWriter("testcsv3.csv", false);
        writer2.write("name,age,like\r\n");
        for (long i=1;i<15299999L;i++){
            writer2.write("张"+i+", 1"+i+", eat"+i+"\r\n");
        }
        writer2.close();
    }
    public static Reader getReader(String relativePath) {

        try {
            return new InputStreamReader(CsvUtils.class.getResourceAsStream(relativePath), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("Unable to read input", e);
        }
    }
//313850 307064



    /**
     * 处理分割cvs
     * @param file
     * @param lines
     * @return
     */
    public static void dealCutCvs( File file, int lines){
        // 将一个文件复制到以这个文件名称命名的文件夹目录下
        File newFile=copyFile(file);
        String os = System.getProperty("os.name");
        if(os.toLowerCase().startsWith("win")){
            cutCvsFile(newFile,lines);
        }else{
            Process proc;
            String [] cmd;
            // -d   如果加上-d则后缀为数字，不加则默认为字母 注意我们linux镜像是没有的
            // -a   默认为2，意思是后缀的位数，这个是根据你分出来的文件个数决定的
            // test_00 是切割的第一个文件
            String path=newFile.getAbsolutePath().substring(0,newFile.getAbsolutePath().lastIndexOf(newFile.getName()));
            // split -l 10000 test.csv  test_
            String shell="cd "+path +" && split -l "+lines+" "+newFile.getName()+" test_";
            log.info("命令是{}",shell);
            cmd= new String[]{"/bin/sh", "-c", shell};
            try {
                proc =Runtime.getRuntime().exec(cmd);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
    /**
     * 预览
     * @param   dest  文件
     * @param   headLineNum 表头的位置
     * @return
     */
    public static DataFilePreVO parseCsv(File dest,int headLineNum){
        DataFilePreVO dataFilePreVO = new DataFilePreVO();
        if(headLineNum < 1) {
            headLineNum = 1;
        }
        // 头
        List<String> title = new ArrayList<>();
        // 5条样本数据
        List<Object> data = new ArrayList<>();
        // 字段类型
        List<Map<String, Integer>> typeList = new ArrayList<>();
        // 读取数据
        readCsv( dest , headLineNum, 1000, title, data,  typeList);

        // 处理预览的数据
        dealDataFilePreVO( dataFilePreVO, title, data, typeList);

        log.info("预览---结束处理csv预览数据");

        return dataFilePreVO;

    }
    /**
     * 处理预览数据
     * @param
     * @return
     */
    private static void dealDataFilePreVO( DataFilePreVO dataFilePreVO, List<String> title, List<Object> data, List<Map<String, Integer>> typeList) {
        log.info("预览---开始处理csv预览数据");
        // 取表头
        List<TableField> titleList = new ArrayList<>();
        // 记录预览数据小数字段名
        List<String> floatFieldNameList = Lists.newArrayList();
        // 数据表字段数
        int columnCount = title.size();
        for (int i = 1; i <= columnCount; i++){
            TableField tableField = new TableField();
            tableField.setFieldNo(i);
            tableField.setFieldName("a" + i);
            String remark = title.get(i - 1);
            if(!StringUtils.isEmpty(remark)){
                tableField.setRemarks(remark);
                String fieldAlias = "";
                if(remark.contains(";")){
                    // 包含分号（英文），截取分号前字符
                    String[] strArray = remark.split(";");
                    fieldAlias = strArray[0];
                }else if(remark.contains("；")){
                    // 包含分号（中文），截取分号前字符
                    String[] strArray = remark.split("；");
                    fieldAlias = strArray[0];
                }else {
                    // 不包含分号，长度大于20截取前20个字符
                    if(remark.length() > 20){
                        fieldAlias = remark.substring(0, 20);
                    }else {
                        fieldAlias = remark;
                    }
                }
                tableField.setFieldAlias(fieldAlias);
            }

            String fieldType = "varchar(30)";
            if(!typeList.isEmpty()){
                Map<String, Integer> columnTypeMap = typeList.get(i-1);
                fieldType = getMaxKeyFromMap(columnTypeMap);
            }

            if (fieldType.contains("varchar")){
                tableField.setFieldLength(getVarcharLength(fieldType));
                tableField.setFieldScale(0);
            }else if (fieldType.contains("decimal")){
                tableField.setFieldLength(getDecimalNumberLength(fieldType));
                tableField.setFieldScale(getDecimalScale(fieldType));
                floatFieldNameList.add("a" + i);
            }else {
                tableField.setFieldLength(20);
                tableField.setFieldScale(0);
            }
            tableField.setFieldType(fieldType);

            // 数据是否超过1000, 超过样本数据字符串长度增加

            titleList.add(tableField);
        }

        // 样本数据
        List<Map<String, String>> sampleList = new ArrayList<>();
        for(int i = 0; i < data.size(); i++ ){
            Map<String, String> value = Maps.newHashMap();
            int k = 1;
            for (String col : (List<String>)data.get(i)) {
                // 格式化小数字符串
                if(floatFieldNameList.contains("a" + k) && !StringUtils.isEmpty(col)){
                    value.put("a" + k, formatFloat(col.trim()));
                }else {
                    value.put("a" + k, col);
                }
                k++;
            }
            sampleList.add(value);
        }
        dataFilePreVO.setSampleData(sampleList);
        dataFilePreVO.setTitleList(titleList);

        // 条件判断：表头是否存在空
        // 导入的表头信息
        List<TableField> importTableFieldList = dataFilePreVO.getTitleList();
        if(importTableFieldList == null || importTableFieldList.size() == 0){
            throw new BDException("表头为空，请选择非空行作为表头");
        }
        // 自动截取表头：截取至最后一列字段别名不为空
        importTableFieldList = cutTitle(importTableFieldList);
        boolean flag = checkTitleExitNull(importTableFieldList);
        if(flag){
            throw new BDException("表头存在为空的列，请检查导入文件");
        }

    }
    /**
     * 检查文件表头是否存在空
     * @return true 存在表头为空  false 不存在表头为空
     */
    public static boolean checkTitleExitNull(List<TableField> titleList){
        if (titleList != null && titleList.size() > 0) {
            for (TableField field : titleList) {
                if(StringUtils.isEmpty(field.getFieldAlias())){
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * 自动截取表头
     * @param titleList
     * @return
     */
    public static List<TableField> cutTitle(List<TableField> titleList){
        /**
         * 获取titleList最后不为空的一列下标，截取
         */
        if (titleList != null && titleList.size() > 0) {
            int index = 0;
            for(int i = titleList.size() -1; i > 0; i--){
                if(!StringUtils.isEmpty(titleList.get(i).getFieldAlias())){
                    index = i;
                    break;
                }
            }

            List<TableField> tableFieldList = Lists.newArrayList();
            for(int i = 0; i <= index ; i++){
                tableFieldList.add(titleList.get(i));
            }
            return tableFieldList;
        }else {
            return Collections.EMPTY_LIST;
        }

    }
    /**
     * 获取字符串长度
     * @param str
     * @return
     */
    private static int getVarcharLength(String str){
        return Integer.parseInt(str.substring(str.indexOf('(') + 1, str.indexOf(')')));
    }
    public static String formatFloat(String value) {
        if (null != value && value.contains(".")) {
            if (isNumeric(value)) {
                try {
                    BigDecimal decimal = new BigDecimal(value);
                    BigDecimal setScale = decimal.setScale(10, BigDecimal.ROUND_HALF_DOWN).stripTrailingZeros();
                    return setScale.toPlainString();
                } catch (Exception e) {
                }
            }
        }
        return value;
    }
    private static boolean isNumeric(String str) {
        Pattern pattern = Pattern.compile("[\\+\\-]?[\\d]+([\\.][\\d]*)?([Ee][+-]?[\\d]+)?$");
        Matcher isNum = pattern.matcher(str);
        if (!isNum.matches()) {
            return false;
        }
        return true;
    }
    /**
     * 获取小数整数部分长度
     * @param str
     * @return
     */
    private static int getDecimalNumberLength(String str){
        return Integer.parseInt(str.substring(str.indexOf('(') + 1, str.indexOf(',')));
    }

    /**
     * 获取小数取数范围长度
     * @param str
     * @return
     */
    private static int getDecimalScale(String str){
        return Integer.parseInt(str.substring(str.indexOf(',') + 1, str.indexOf(')')));
    }

    private static String getMaxKeyFromMap(Map<String, Integer> map){
        String maxType = "";
        int value = -1;
        int count = 0;
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            if (count == 0){
                maxType = entry.getKey();
                value = entry.getValue();
            }else{
                if (entry.getValue() >= value){
                    maxType = entry.getKey();
                }
            }
            count++;
        }
        return maxType;
    }
    /**
     * 读取csv
     * @param
     * @return
     */
    public static void readCsv(File csvFile ,int headLineNum,int readCount, List<String> title,  List<Object> data, List<Map<String, Integer>> typeList){
        log.info("预览---开始读取csv数据");
        long startTime=System.currentTimeMillis();
        // 创建csv解析器settings配置对象
        CsvParserSettings settings = new CsvParserSettings();
        // 文件中使用 '\n' 作为行分隔符
        // 确保像MacOS和Windows这样的系统
        // 也可以正确处理（MacOS使用'\r'；Windows使用'\r\n'）
        settings.getFormat().setLineSeparator("\n");
        CsvParser csvParser = new CsvParser(settings);
        String path=csvFile.getAbsolutePath().substring(0,csvFile.getAbsolutePath().lastIndexOf("."))+File.separator+"test_aa";
        if(!new File(path).exists()){
            path=csvFile.getAbsolutePath();
        }
        csvParser.beginParsing(new File(path),"UTF-8");
        String[] row;
        int j=0;
        log.info("预览---开始处理读取csv数据");
        while ((row = csvParser.parseNext()) != null&&j<readCount) {
            dealReadCsv(Arrays.asList(row),  headLineNum,  csvParser.getContext().currentLine() , readCount,title,  data, typeList);
            j++;
        }
        log.info("预览读取csv文件速度：{}", System.currentTimeMillis()-startTime);
    }
    /**
     *
     * @param row
     * @param headLineNum 表头的行数
     * @param rowIndex 当前行数
     * @param readCount 读取的行数
     * @param title 表头
     * @param data 数据
     * @param typeList 类型list
     * @return
     */
    public static void dealReadCsv(List<String> row, int headLineNum, Long rowIndex ,int readCount,List<String> title, List<Object> data,List<Map<String, Integer>> typeList){

        // 判断是否超过样本数据量 1000
        if (rowIndex <= readCount + headLineNum){
            if(rowIndex == headLineNum){
                row.forEach(s -> title.add(s));
            }else {
                if(data.size() < 5){
                    data.add(row);
                }
                // 字段类型分析处理: 主要是正则表达匹配 默认字符串varchar(30)
                Map<String, Integer> typeMap;
                if(title.size() > 0){
                    List<String> list =row;
                    for (int i=0; i < title.size();i++){
                        String type = "varchar(20)";
                        String content = i < list.size() ? list.get(i) : null;
                        if(content != null && !StringUtils.isEmpty(content)){
                            // 正则处理
                            if (content.matches("^(\\d{4}-\\d{2}-\\d{2})")) {
                                // 日期格式 2019-03-07
                                type = "date";
                            } else if (content.matches("^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})")) {
                                // 时间格式 2019-03-07 15:15:00
                                type = "datetime";
                            } else if (content.matches("^(\\d{2}:\\d{2}:\\d{2})")) {
                                // 时间格式 15:15:00
                                type = "time";
                            } else if(content.matches("^[1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*$")
                                    || content.matches("^-[1-9]\\d*\\.\\d*|-0\\.\\d*[1-9]\\d*$")){
                                // 正负浮点数
                                type = "decimal(20,3)";
                            }else if(content.matches("^[1-9]\\d*|0$") || content.matches("^-[1-9]\\d*$")){
                                // 正负数 ,0
                                // 数值类型作为字符串处理
                                int length = content.length();
                                type = "varchar(" + length +")";
                            } else {
                                // 字符串长度处理
                                int length = content.length();
                                type = "varchar(" + length +")";
                            }
                        }else {
                            // 空单元格不做处理
                        }

                        if (typeList.size() > i) {
                            typeMap = typeList.get(i);
                            // 只保留一个 varchar 信息
                            if (type.startsWith("varchar")) {
                                for (String key : typeMap.keySet()) {
                                    if (key.startsWith("varchar")) {
                                        if (type.length() > key.length()) {
                                            // 长度大的
                                            typeMap.put(type, typeMap.get(key));
                                            typeMap.remove(key);
                                            break;
                                        } else if (type.length() == key.length()) {
                                            if (type.compareTo(key) > 0) {
                                                // 长度相等的，判断数字
                                                typeMap.put(type, typeMap.get(key));
                                                typeMap.remove(key);
                                                break;
                                            }
                                        }
                                    }
                                }
                            } else {
                                // type 不是 varchar 开头
                                if (typeMap.containsKey(type)) {
                                    int mValue = typeMap.get(type);
                                    typeMap.put(type, ++mValue);
                                } else {
                                    typeMap.put(type, 1);
                                }
                            }
                        }else {
                            typeMap = new HashMap<>();
                            typeMap.put(type, 1);
                        }

                        if (typeList.size() > i) {
                            typeList.set(i, typeMap);
                        } else {
                            typeList.add(typeMap);
                        }
                    }
                }
            }
        }else {
            log.info("预览采样完成，退出 csv 解析 - 忽略该异常");
        }
    }




    /**
     * 将一个文件复制到以这个文件名称命名的文件夹目录下
     * @param   file
     * @return
     */
    private static File copyFile(File file) {
        FileChannel inputChannel = null;
        FileChannel outputChannel = null;
        File tempFile= null;
        try {
            // 将文件copy到一个目录下面，再进行切割
            String path=file.getAbsolutePath().substring(0,file.getAbsolutePath().lastIndexOf("."));
            if (!new File(path).exists()) {
                new File(path).mkdirs();
            }
            String tempFilePath=path+File.separator+file.getName();
            tempFile = new File(tempFilePath);
            inputChannel = new FileInputStream(file).getChannel();
            outputChannel = new FileOutputStream(tempFile).getChannel();
            outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputChannel.close();
                outputChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return tempFile;
    }


    /**
     * 分割cvs文件
     * @param
     * @return
     */
    public static  void cutCvsFile(File file,int lines)
    {
        try{
            Long startTime=System.currentTimeMillis();
            int count = 0;
            // 创建csv解析器settings配置对象
            CsvParserSettings settings = new CsvParserSettings();
            settings.getFormat().setLineSeparator("\n");
            CsvParser csvParser = new CsvParser(settings);
            csvParser.beginParsing(file,"UTF-8");
            while (( csvParser.parseNext()) != null) {
                count++;
            }
            csvParser.stopParsing();
            int totalLine=0;
            if((count%lines)==0){
                totalLine= (count/lines);
            }
            else{
                totalLine=(count/lines)+1;
            }
            cutCvsFile(file,lines,totalLine);
            System.out.println("csv分割文件耗时"+(System.currentTimeMillis()-startTime));
        }catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void cutCvsFile (File file,int lines, int totalLine) throws FileNotFoundException, IOException{

        // 创建csv解析器settings配置对象
        CsvParserSettings settings = new CsvParserSettings();
        // 文件中使用 '\n' 作为行分隔符
        // 确保像MacOS和Windows这样的系统
        // 也可以正确处理（MacOS使用'\r'；Windows使用'\r\n'）
        settings.getFormat().setLineSeparator("\n");
        CsvParser csvParser = new CsvParser(settings);
        csvParser.beginParsing(file,"UTF-8");
        String path=file.getAbsolutePath().substring(0,file.getAbsolutePath().lastIndexOf(file.getName()));

        String[] row;
        String strLine = null;
        int count=1;
        String num;
        for (int i=0;i<totalLine;i++) {
            num = getKey(i);
            if(num.length()<2){
                num="a"+num;
            }
            FileWriter fstream1 = new FileWriter(path+"/test_"+num);
            BufferedWriter out = new BufferedWriter(fstream1);
            for(int j=0;j<lines;j++){
                if ((row = csvParser.parseNext()) != null) {
                    strLine = StringUtils.join(Arrays.asList(row), ",");
                    if (strLine!= null) {
                        out.write(strLine);
                        out.newLine();
                    }
                }
                count++;
            }
            out.close();
        }
    }

    /**
     * 获取 a,b,c,d,e,f,g的排序
     * @param   index
     * @return
     */
    public static String getKey(int index){
        String colCode = "";
        char key='a';
        int loop = index / 26;
        if(loop>0){
            colCode += getKey(loop-1);
        }
        key = (char) (key+index%26);
        colCode += key;
        return colCode;
    }
    /**
     * 生成excel
     * @param
     * @return
     */
    @Test
    public void te(){
        // 通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getWriter("/writeBeanTest.xlsx");

        //自定义标题别名
        writer.addHeaderAlias("name", "姓名");
        writer.addHeaderAlias("age", "年龄");
        writer.addHeaderAlias("like", "爱好");
        Map<String, Object> map=new HashMap<>();
        map=new HashMap<>();
        map.put("name","姓名");
        map.put("age","年龄");
        map.put("like","爱好");
        writer.write(Collections.singleton(map), false);
        String[] row=null;
        for (Long i=1L;i<=3299998L;i++){
            map=new HashMap<>();
            map.put("name","张"+i);
            map.put("age","1"+i);
            map.put("like","eat"+i);
            writer.write(Collections.singleton(map), false);
        }

// 关闭writer，释放内存
        writer.close();
    }
}


